package com.sxy.tools;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 操作Word文档的工具类
 *
 * @author shexianyu
 */
public class WordUtils {

    private static final Logger LOG = LoggerFactory.getLogger(WordUtils.class);

    /**
     * 根据文件路径获取XWPFDocument对象
     *
     * @param filePath 文件路径
     * @return XWPFDocument
     */
    public static XWPFDocument loadDocument(String filePath) {
        LOG.info("加载Word文档路径: {}", filePath);
        //1.根据文件路径获取文件后缀
        String fileExtension = StringUtils.substringAfterLast(filePath, ".");
        //2.根据文件后缀判断文件类型是否为docx或doc
        if (!"docx".equalsIgnoreCase(fileExtension) && !"doc".equalsIgnoreCase(fileExtension)) {
            LOG.error("文件类型错误，只支持docx或doc文件");
            throw new RuntimeException("文件类型错误，只支持docx或doc文件");
        }
        //3.加载Word文档
        try (FileInputStream fis = new FileInputStream(filePath)) {
            //4.返回XWPFDocument对象
            return new XWPFDocument(fis);
        } catch (FileNotFoundException e) {
            LOG.error("根据文件路径{}未找到文件", filePath, e);
            throw new RuntimeException("根据文件路径【" + filePath + "】未找到文件", e);
        } catch (IOException e) {
            LOG.error("加载Word文档失败", e);
            throw new RuntimeException("加载Word文档失败", e);
        }
    }

    /**
     * 保存XWPFDocument对象到指定路径
     *
     * @param document   XWPFDocument
     * @param targetPath 目标文件路径
     */
    public static void saveDocument(XWPFDocument document, String targetPath) {
        LOG.info("保存Word文档路径: {}", targetPath);
        //1.判断路径是否存在,不存在则创建
        File file = new File(targetPath);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        //2.保存文档
        try (FileOutputStream out = new FileOutputStream(targetPath)) {
            document.write(out);
        } catch (IOException e) {
            throw new RuntimeException("保存文件失败", e);
        }
    }

    /**
     * 合并文档中所有段落中的所有run
     *
     * @param document  XWPFDocument
     * @param startChar 开始字符
     * @param endChar   结束字符
     */
    public static void mergeParagraphRuns(XWPFDocument document, String startChar, String endChar) {
        //1.遍历所有段落
        for (XWPFParagraph paragraph : document.getParagraphs()) {
            mergeRuns(paragraph, startChar, endChar);
        }
        //2.遍历所有表格
        for (XWPFTable table : document.getTables()) {
            for (XWPFTableRow row : table.getRows()) {
                for (XWPFTableCell tableCell : row.getTableCells()) {
                    for (XWPFParagraph cellParagraph : tableCell.getParagraphs()) {
                        mergeRuns(cellParagraph, startChar, endChar);
                    }
                }
            }
        }
    }

    /**
     * 合并文档中每个段落的run分为多段的情况
     *
     * @param paragraph XWPFParagraph
     * @param startChar 开始字符
     * @param endChar   结束字符
     */
    private static void mergeRuns(XWPFParagraph paragraph, String startChar, String endChar) {
        LOG.info("识别文档中的占位符：开始字符: {}, 结束字符: {}", startChar, endChar);
        LOG.info("段落内容: {}", paragraph.getText());
        List<XWPFRun> runs = paragraph.getRuns();
        for (int i = 0; i < runs.size(); i++) {
            XWPFRun run = runs.get(i);
            StringBuilder text = new StringBuilder(run.getText(0));
            LOG.info("run内容:{}, {}", i, text);
            //判断开始字符和结束字符是否闭环,若闭环则跳过
            if (containsCompleteClosedLoop(text, startChar, endChar)) {
                continue;
            }
            //判断字符是否包含开始字符,若包含,则处理后续拼接操作
            if (text.toString().contains(startChar)) {
                for (int j = i + 1; j < runs.size(); j++) {
                    XWPFRun xwpfRun = runs.get(j);
                    String runText = xwpfRun.getText(0);
                    LOG.info("run内容:{}, {}", j, runText);
                    //清空run内容
                    run.setText("", 0);
                    xwpfRun.setText("", 0);
                    //判断字符包含结束占位符并且不包含开始占位符,则拼接run内容
                    if (runText.contains(endChar) && !runText.contains(startChar)) {
                        text.append(runText);
                        run.setText(text.toString(), 0);
                        i = j;
                        break;
                    } else {
                        text.append(runText);
                    }
                }
            }
        }
    }

    /**
     * 判断字符是否闭环
     *
     * @param input     字符
     * @param startChar 开始字符
     * @param endChar   结束字符
     */
    public static boolean containsCompleteClosedLoop(StringBuilder input, String startChar, String endChar) {
        // 记录已打开的"【"的数量
        int openCount = 0;
        // 标记是否在闭环中
        boolean inLoop = false;
        for (int i = 0; i < input.length(); i++) {
            char c = input.charAt(i);
            if (c == startChar.charAt(0)) {
                if (inLoop) {
                    // 如果已经在闭环中，说明出现了嵌套的"【"，不合法
                    return false;
                }
                openCount++;
                inLoop = true;
            } else if (c == endChar.charAt(0)) {
                if (!inLoop) {
                    // 如果不在闭环中，直接出现"】"，不合法
                    return false;
                }
                openCount--;
                if (openCount == 0) {
                    // 如果openCount为0，说明找到了一个完整的闭环
                    inLoop = false;
                }
            }
        }
        // 检查是否所有"【"都被"】"闭合
        return openCount == 0;
    }

    /**
     * 识别文档中的占位符,将其作为完整的参数放入list集合中
     *
     * @param document   文档对象
     * @param regExp     正则表达式
     * @param isTrimChar 是否去除字符串两端的字符串
     * @return 占位符集合
     */
    public static List<String> identifyPlaceholders(XWPFDocument document, String regExp, boolean isTrimChar) {
        //1.初始化占位符集合
        List<String> placeholders = new ArrayList<>();
        //2.创建正则表达式模式
        Pattern pattern = Pattern.compile(regExp);
        //3.遍历文档中的段落
        for (XWPFParagraph paragraph : document.getParagraphs()) {
            addPlaceholder(placeholders, pattern, paragraph, isTrimChar);
        }
        //4.遍历文档中的表格
        for (XWPFTable table : document.getTables()) {
            for (XWPFTableRow row : table.getRows()) {
                for (XWPFTableCell cell : row.getTableCells()) {
                    for (XWPFParagraph paragraph : cell.getParagraphs()) {
                        addPlaceholder(placeholders, pattern, paragraph, isTrimChar);
                    }
                }
            }
        }
        //5.返回占位符集合
        return placeholders;
    }

    /**
     * 添加匹配占位符到集合中
     *
     * @param placeholders 占位符集合
     * @param pattern      正则表达式模式
     * @param paragraph    段落
     * @param isTrimChar   是否去除字符串起始字符
     */
    public static void addPlaceholder(List<String> placeholders, Pattern pattern, XWPFParagraph paragraph, boolean isTrimChar) {
        //1.遍历段落中的所有run
        for (XWPFRun run : paragraph.getRuns()) {
            //2.获取run中的文本
            String text = run.getText(0);
            if (StringUtils.isNotBlank(text)) {
                //3.判断文本是否匹配正则表达式
                Matcher matcher = pattern.matcher(text);
                //4.匹配成功则添加到集合中
                while (matcher.find()) {
                    //5.获取匹配到的占位符
                    String placeholder = matcher.group();
                    LOG.info("获取匹配的占位符: {}", placeholder);
                    if (isTrimChar) {
                        placeholder = placeholder.substring(1, placeholder.length() - 1);
                    }
                    LOG.info("获取匹配的占位符,去掉首尾字符串: {}", placeholder);
                    //6.如果集合中不存在该占位符，则添加到集合中
                    if (!placeholders.contains(placeholder)) {
                        placeholders.add(placeholder);
                    }
                }
            }
        }
    }

    /**
     * 替换文档中的占位符
     *
     * @param document     文档对象
     * @param placeholders 文档中的所有占位符
     * @param dataMap      替换数据
     */
    public static void replacePlaceholdersInDocument(XWPFDocument document, List<String> placeholders, Map<String, Object> dataMap) {
        //1.遍历文档中的所有段落
        for (XWPFParagraph paragraph : document.getParagraphs()) {
            replacePlaceholders(paragraph, placeholders, dataMap);
        }
        //2.遍历文档中的所有表格
        for (XWPFTable table : document.getTables()) {
            for (XWPFTableRow row : table.getRows()) {
                for (XWPFTableCell tableCell : row.getTableCells()) {
                    for (XWPFParagraph paragraph : tableCell.getParagraphs()) {
                        replacePlaceholders(paragraph, placeholders, dataMap);
                    }
                }
            }
        }
    }

    /**
     * 替换段落中的占位符
     *
     * @param paragraph    段落
     * @param placeholders 占位符集合
     * @param dataMap      数据集合
     */
    public static void replacePlaceholders(XWPFParagraph paragraph, List<String> placeholders, Map<String, Object> dataMap) {
        //1.遍历段落中的所有run
        for (XWPFRun run : paragraph.getRuns()) {
            //2.获取run中的文本
            String text = run.getText(0);
            //3.遍历占位符集合
            for (String placeholder : placeholders) {
                //4.判断文本中是否包含占位符
                if (text.contains(placeholder)) {
                    //5.替换占位符为对应的数据
                    String data = null;
                    if (dataMap.get(placeholder) instanceof String) {
                        data = (String) dataMap.get(placeholder);
                    }
                    if (data != null) {
                        text = text.replace(placeholder, data);
                        run.setText(text, 0);
                    }
                }
            }
        }
    }
}
